#if WITH_JIGLIBX

using System;
using System.Collections.Generic;

using JigLibX.Physics;
using JigLibX.Collision;
using JigLibX.Geometry;
using JigLibXPlane = JigLibX.Geometry.Plane;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

using Tomahawk.Runtime.Logic;
using Tomahawk.Runtime.Resources;
using Tomahawk.Resources.Physics;
using Tomahawk.Runtime.Physics.Interfaces;


namespace Tomahawk.Runtime.Physics.JigLibX
{
    public class PhysicsBody : Body
    {
        private PhysicsActor actor;
        public PhysicsActor Actor { get { return actor; } }

        private string name;
        public string Name { get { return name; } }

        public PhysicsBody(string name, PhysicsActor actor)
            : base()
        {
            this.name = name;
            this.actor = actor;
        }
    }

    public class PhysicsActor : LoadableResource, IPhysicsActor
    {
        #region Properties
        protected string resource;
        protected XObject data = null;
        protected PhysicsActorType type = PhysicsActorType.StaticActor;
        protected Dictionary<string, PhysicsBody> bodies = new Dictionary<string, PhysicsBody>();
        protected PhysicsScene scene = null;
        protected bool loaded = false;
        protected bool enabled = false;

        public bool Enabled
        {
            get { return enabled; }
            set 
            {
                if (IsReady)
                {
                    foreach (KeyValuePair<string, PhysicsBody> pair in bodies)
                    {
                        if (value)
                        {
                            pair.Value.EnableBody();
                        }
                        else
                        {
                            pair.Value.DisableBody();
                        }
                    }

                    enabled = true;
                }
            }
        }
        #endregion

        public PhysicsActor(string resource, PhysicsScene physScene)
        {
            this.resource = resource;
            this.scene = physScene;
        }

        private void Verify(PhysicsScene.Status validStatus)
        {
            if (scene == null || scene.CurrentStatus != validStatus)
            {
                throw new Exception("Invalid operation while simulating physics");
            }
        }

        #region Collisions
        public bool OnCollision(CollisionSkin skin0, CollisionSkin skin1)
        {
            bool skip = true;
            IPhysicsActor actor0 = (skin0.Owner as PhysicsBody).Actor;
            IPhysicsActor actor1 = (skin1.Owner as PhysicsBody).Actor;

            bool collidable0 = actor0.PhysicsType != PhysicsActorType.TriggerActor;
            bool collidable1 = actor1.PhysicsType != PhysicsActorType.TriggerActor;

            // Add a collision
            if (collidable0 && collidable1)
            {
                Collision collision = new Collision();
                collision.actor0 = (skin0.Owner as PhysicsBody).Actor;
                collision.actor1 = (skin1.Owner as PhysicsBody).Actor;
                scene.Collisions.Enqueue(collision);
                skip = false;
            }

            // Add a contact
            else if (collidable0 || collidable1)
            {
                bool found = false;
                IPhysicsActor trigger = collidable0 ? actor1 : actor0;
                IPhysicsActor actor = collidable0 ? actor0 : actor1;

                // If the contact was already in the list, we just have to update the type flag
                foreach (Contact contact in scene.Contacts)
                {
                    if ((contact.trigger == trigger) && (contact.actor == actor))
                    {
                        if (contact.type != Contact.ContactType.Enter)
                        {
                            contact.type = Contact.ContactType.Stay;
                        }
                        found = true;
                        break;
                    }
                }

                // Otherwise, we have to add a new contact to the list
                if (!found)
                {
                    Contact contact = new Contact();
                    contact.type = Contact.ContactType.Enter;
                    contact.trigger = trigger;
                    contact.actor = actor;

                    scene.Contacts.Enqueue(contact);
                }
                
                skip = true;
            }

            return !skip;
        }
        #endregion

        #region LoadableResource
        protected override bool OnLoad(GraphicsDevice graphics, ContentManager contentManager)
        {
            // Load the actor descriptor
            PhysicsActorDesc actorDesc = contentManager.Load<PhysicsActorDesc>(resource);

            if (actorDesc == null)
            {
                return false;
            }

            // Get the desired type for the actor
            type = actorDesc.type;

            // Create the bodies defined in the descriptor
            foreach (PhysicsBodyDesc bodyDesc in actorDesc.bodies)
            {
                // Create a body and a collision skin
                PhysicsBody body = new PhysicsBody(bodyDesc.name, this);
                CollisionSkin skin = new CollisionSkin(body);
                body.CollisionSkin = skin;
                body.Mass = bodyDesc.mass;
                bodies.Add(bodyDesc.name, body);


                if (actorDesc.type == PhysicsActorType.StaticActor)
                {
                    body.Immovable = true;
                }

                // Register to the collision event of the new skin
                if (actorDesc.type != PhysicsActorType.StaticActor)
                {
                    skin.callbackFn += OnCollision;
                }

                // Add primitives to the body
                foreach (PhysicsShapeDesc shapeDesc in bodyDesc.shapes)
                {
                    // Create the material for this primitive
                    int materialID = (int)shapeDesc.materialID;
                    MaterialProperties materialProperties = new MaterialProperties(shapeDesc.elasticity, shapeDesc.staticRoughness, shapeDesc.dynamicRoughness);

                    // Add a sphere primitive
                    if (shapeDesc.GetType() == typeof(PhysicsSphereShapeDesc))
                    {
                        PhysicsSphereShapeDesc sphereDesc = shapeDesc as PhysicsSphereShapeDesc;
                        /*skin.AddPrimitive(
                            new Sphere(sphereDesc.localTransform, sphereDesc.radius),
                            materialID, materialProperties);*/
                        skin.AddPrimitive(
                            new Sphere(sphereDesc.localTransform, sphereDesc.radius),
                            materialProperties);
                    }

                    // Add a box primitive
                    else if (shapeDesc.GetType() == typeof(PhysicsBoxShapeDesc))
                    {
                        PhysicsBoxShapeDesc boxDesc = shapeDesc as PhysicsBoxShapeDesc;
                        /*skin.AddPrimitive(
                            new Box(boxDesc.localTransform, Matrix.CreateFromQuaternion(boxDesc.localRotation), boxDesc.Extents),
                            materialID, materialProperties);*/
                        skin.AddPrimitive(
                            new Box(boxDesc.localTransform, Matrix.CreateFromQuaternion(boxDesc.localRotation), boxDesc.Extents),
                            materialProperties);
                    }

                    // Add a capsule primitive
                    else if (shapeDesc.GetType() == typeof(PhysicsCapsuleShapeDesc))
                    {
                        PhysicsCapsuleShapeDesc capsuleDesc = shapeDesc as PhysicsCapsuleShapeDesc;
                        /*skin.AddPrimitive(
                            new Capsule(capsuleDesc.localTransform, Matrix.CreateFromQuaternion(capsuleDesc.localRotation), capsuleDesc.radius, capsuleDesc.height),
                            materialID, materialProperties);*/
                        skin.AddPrimitive(
                            new Capsule(capsuleDesc.localTransform, Matrix.CreateFromQuaternion(capsuleDesc.localRotation), capsuleDesc.radius, capsuleDesc.height),
                            materialProperties);
                    }

                    // Add a plane primitive
                    else if (shapeDesc.GetType() == typeof(PhysicsPlaneShapeDesc))
                    {
                        PhysicsPlaneShapeDesc planeDesc = shapeDesc as PhysicsPlaneShapeDesc;
                        /*skin.AddPrimitive(
                            new JigLibXPlane(planeDesc.up, planeDesc.localTransform),
                            materialID, materialProperties);*/
                        skin.AddPrimitive(
                            new JigLibXPlane(planeDesc.up, planeDesc.localTransform),
                            materialProperties);
                    }

                    // Add a mesh primitive
                    else if (shapeDesc.GetType() == typeof(PhysicsMeshShapeDesc))
                    {
                        PhysicsMeshShapeDesc meshDesc = shapeDesc as PhysicsMeshShapeDesc;

                        List<TriangleVertexIndices> tviList = new List<TriangleVertexIndices>(meshDesc.indexList.Count);
                        foreach (Vector3 indices in meshDesc.indexList)
                        {
                            tviList.Add(new TriangleVertexIndices((int)indices.X, (int)indices.Y, (int)indices.Z));
                        }

                        TriangleMesh triangleMesh = new TriangleMesh();
                        triangleMesh.CreateMesh(meshDesc.vertexList, tviList, 4, 1.0f);

                        /*skin.AddPrimitive(
                            triangleMesh,
                            materialID, materialProperties);*/
                        skin.AddPrimitive(
                            triangleMesh,
                            materialProperties);
                    }

                    // Error handling
                    else
                    {
                        // @TODO [CLOPEZ]: Error Handling
                    }
                }

                // Add the body to the physics scene
                body.EnableBody();
            }

            enabled = true;
            loaded = true;

            return true;
        }
        #endregion

        #region IPhysicsActor
        #region Properties
        public PhysicsActorType PhysicsType
        {
            get { return type; }
        }

        public XObject Data
        {
            get { return data; }
            set { data = value; }
        }

        public Vector3 GetLocation(string bodyName)
        {
            Verify(PhysicsScene.Status.Waiting);

            PhysicsBody body = null;
            if (bodies.TryGetValue(bodyName, out body))
            {
                return body.Position;
            }

            return Vector3.Zero;
        }

        public Quaternion GetRotation(string bodyName)
        {
            Verify(PhysicsScene.Status.Waiting);

            PhysicsBody body = null;
            if (bodies.TryGetValue(bodyName, out body))
            {
                return Quaternion.CreateFromRotationMatrix(body.Orientation);
            }

            return Quaternion.Identity;
        }

        public void SetMass(float mass, string bodyName)
        {
            Verify(PhysicsScene.Status.Waiting);

            PhysicsBody body = null;
            if (bodies.TryGetValue(bodyName, out body))
            {
                PrimitiveProperties primitiveProperties =
                    new PrimitiveProperties(PrimitiveProperties.MassDistributionEnum.Solid, PrimitiveProperties.MassTypeEnum.Density, mass);

                float junk; 
                Vector3 com; 
                Matrix it, itCoM;

                CollisionSkin skin = body.CollisionSkin;
                skin.GetMassProperties(primitiveProperties, out junk, out com, out it, out itCoM);
                body.BodyInertia = itCoM;
                body.Mass = junk;
            }
        }

        public float GetMass(string bodyName)
        {
            Verify(PhysicsScene.Status.Waiting);

            PhysicsBody body = null;
            if (bodies.TryGetValue(bodyName, out body))
            {
                return body.Mass;
            }
            return 0.0f;
        }
        #endregion

        #region Movement
        public bool MoveTo(string bodyName, Vector3 location, Quaternion rotation)
        {
            Verify(PhysicsScene.Status.Waiting);

            PhysicsBody body = null;
            if (bodies.TryGetValue(bodyName, out body))
            {
                body.MoveTo(location, Matrix.CreateFromQuaternion(rotation));
                return true;
            }

            return false;
        }
        #endregion

        #region Forces
        public bool AddWorldForce(string bodyName, Vector3 force, Vector3 position)
        {
            Verify(PhysicsScene.Status.Waiting);

            PhysicsController.Force input = new PhysicsController.Force();
            if (bodies.TryGetValue(bodyName, out input.body))
            {
                input.coordinateSystem = PhysicsController.CoordinateSystem.WorldCoordinates;
                input.force = force;
                input.position = position;

                scene.PhysicsController.Forces.Enqueue(input);
            }

            return true;
        }

        public bool AddLocalForce(string bodyName, Vector3 force, Vector3 position)
        {
            Verify(PhysicsScene.Status.Waiting);

            PhysicsController.Force input = new PhysicsController.Force();
            if (bodies.TryGetValue(bodyName, out input.body))
            {
                input.coordinateSystem = PhysicsController.CoordinateSystem.LocalCoordinates;
                input.force = force;
                input.position = position;

                scene.PhysicsController.Forces.Enqueue(input);
            }

            return true;
        }

        public bool AddWorldTorque(string bodyName, Vector3 torque)
        {
            Verify(PhysicsScene.Status.Waiting);

            PhysicsController.Torque input = new PhysicsController.Torque();
            if (bodies.TryGetValue(bodyName, out input.body))
            {
                input.coordinateSystem = PhysicsController.CoordinateSystem.WorldCoordinates;
                input.torque = torque;

                scene.PhysicsController.Torques.Enqueue(input);
            }

            return true;
        }

        public bool AddLocalTorque(string bodyName, Vector3 torque)
        {
            Verify(PhysicsScene.Status.Waiting);

            PhysicsController.Torque input = new PhysicsController.Torque();
            if (bodies.TryGetValue(bodyName, out input.body))
            {
                input.coordinateSystem = PhysicsController.CoordinateSystem.LocalCoordinates;
                input.torque = torque;
            }

            scene.PhysicsController.Torques.Enqueue(input);

            return true;
        }

        #endregion

        #endregion
    }
}

#endif